{\rtf0\ansi{\fonttbl\f0\fswiss Helvetica;\f1\fmodern Ohlfs;}
\paperw13740
\paperh13740
\margl40
\margr40
{\colortbl;\red255\green0\blue0;\red0\green0\blue0;\red255\green109\blue0;\red0\green41\blue0;\red61\green0\blue0;\red0\green255\blue0;}
\pard\tx520\tx1060\tx1600\tx2120\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\f0\b0\i\ulnone\fs28\gray300\fc1\cf1 // Controller.m
\i0\gray0\fc2\cf2 \
\

\b #import
\b0  
\gray550\fc3\cf3 "Controller.h"
\gray0\fc2\cf2 \

\b #import
\b0  
\gray550\fc3\cf3 "regex.h"
\gray0\fc2\cf2 \

\b #import
\b0  <strings.h>\

\b #import
\b0  <stdlib.h>\
\

\fs36 @implementation Controller
\fs28 \
\

\fs36 - init\

\fs28 \{\
	[
\b super
\b0  init];\
	itemList = [[List alloc] init];\
	
\b return
\b0  
\b self
\b0 ;\
\}\
\

\fs36 - free\

\fs28 \{\
	[itemList free];\
	
\b return
\b0  [
\b super
\b0  free];\
\}\
\

\fs36 - appDidInit:sender\

\fs28 \{\
	
\b static
\b0  NXDefaultsVector RTFSyntaxDefaults = \{\
		\{
\gray550\fc3\cf3 "configuration"
\gray0\fc2\cf2 , 
\gray550\fc3\cf3 ""
\gray0\fc2\cf2 \},\
		\{
\gray93\fc4\cf4 NULL
\gray0\fc2\cf2 \}\
	\};\
	\
	
\i\gray300\fc1\cf1 // make this object the services delegate
\i0\gray0\fc2\cf2 \
	[[NXApp appListener] setServicesDelegate:
\b self
\b0 ];\
	\
	
\i\gray300\fc1\cf1 // setup text objects
\i0\gray0\fc2\cf2 \
	[regularExpression setMonoFont:
\gray93\fc4\cf4 YES
\gray0\fc2\cf2 ];\
	[matchedSampleText setMonoFont:
\gray93\fc4\cf4 YES
\gray0\fc2\cf2 ];\
	[unmatchedSampleText setMonoFont:
\gray93\fc4\cf4 YES
\gray0\fc2\cf2 ];\
	\
	
\i\gray300\fc1\cf1 // register defaults
\i0\gray0\fc2\cf2 \
	NXRegisterDefaults(
\gray550\fc3\cf3 "RTFSyntax"
\gray0\fc2\cf2 , RTFSyntaxDefaults);\
	\
	
\i\gray300\fc1\cf1 // read defaults
\i0\gray0\fc2\cf2 \
	[defaultConfiguration setStringValue:NXGetDefaultValue(
\gray550\fc3\cf3 "RTFSyntax"
\gray0\fc2\cf2 , 
\gray550\fc3\cf3 "configuration"
\gray0\fc2\cf2 )];\
\
	
\i\gray300\fc1\cf1 // open the default configuration (or a new one if there is no default)
\i0\gray0\fc2\cf2 \
	
\b if
\b0  (!fileOpened && ![
\b self
\b0  openDefaultConfiguration])\
		[
\b self
\b0  newConfiguration:
\b self
\b0 ];\
		\
	
\b return
\b0  
\b self
\b0 ;\
\}\
\

\fs36 - (int)app:sender openFile:(const char *)path type:(const char *)type\

\fs28 \{\
	
\b if
\b0  (strcmp(type, 
\gray550\fc3\cf3 "syntax"
\gray0\fc2\cf2 ))\
		
\b return
\b0  
\gray93\fc4\cf4 NO
\gray0\fc2\cf2 ;\
		\
	[
\b self
\b0  openNamedConfiguration:path];\
	[configurationWindow makeKeyAndOrderFront:
\b self
\b0 ];\
	fileOpened = 
\gray93\fc4\cf4 YES
\gray0\fc2\cf2 ;\
\
	
\b return
\b0  
\gray93\fc4\cf4 YES
\gray0\fc2\cf2 ;\
\}\
\

\fs36 - (BOOL)appAcceptsAnotherFile:(Application *)sender\

\fs28 \{\
    
\b return
\b0  
\gray93\fc4\cf4 YES
\gray0\fc2\cf2 ;\
\}\
\

\fs36 - previousItem:sender\

\fs28 \{\
	
\b int
\b0  i = [itemNumber intValue];\
	
\b if
\b0  (i <= 
\gray71\fc5\cf5 1
\gray0\fc2\cf2 )\
		
\b return
\b0  
\b self
\b0 ;\
	[
\b self
\b0  saveCurrentItem];\
	[
\b self
\b0  displayItem:i
\gray71\fc5\cf5 -1
\gray0\fc2\cf2 ];\
	
\b return
\b0  
\b self
\b0 ;\
\}\
\

\fs36 - nextItem:sender\

\fs28 \{\
	
\b int
\b0  i = [itemNumber intValue];\
	\
	
\b if
\b0  (i >= [numberOfItems intValue])\
		
\b return
\b0  
\b self
\b0 ;\
	[
\b self
\b0  saveCurrentItem];\
	[
\b self
\b0  displayItem:i+
\gray71\fc5\cf5 1
\gray0\fc2\cf2 ];\
	
\b return
\b0  
\b self
\b0 ;\
\}\
\

\fs36 - deleteItem:sender\

\fs28 \{\
	
\b int
\b0  i = [itemNumber intValue];\
	\
	
\b if
\b0  ([numberOfItems intValue] == 
\gray71\fc5\cf5 1
\gray0\fc2\cf2 )\
		
\b return
\b0  
\b self
\b0 ;	
\i\gray300\fc1\cf1 // can't delete last item (need at least one)
\i0\gray0\fc2\cf2 \
	[[itemList removeObjectAt:i
\gray71\fc5\cf5 -1
\gray0\fc2\cf2 ] free];\
	[numberOfItems setIntValue:[numberOfItems intValue]-
\gray71\fc5\cf5 1
\gray0\fc2\cf2 ];\
	
\b if
\b0  (i == 
\gray71\fc5\cf5 1
\gray0\fc2\cf2 )\
		[
\b self
\b0  displayItem:
\gray71\fc5\cf5 1
\gray0\fc2\cf2 ];\
	
\b else
\b0 \
		[
\b self
\b0  displayItem:i
\gray71\fc5\cf5 -1
\gray0\fc2\cf2 ];\
	
\b return
\b0  
\b self
\b0 ;\
\}\
\

\fs36 - insertItem:sender\

\fs28 \{\
	
\b int
\b0  i = [itemNumber intValue];\
	\
	[
\b self
\b0  saveCurrentItem];\
	[itemList insertObject:[[SyntaxItem alloc] init] at:i
\gray71\fc5\cf5 -1
\gray0\fc2\cf2 ];\
	[numberOfItems setIntValue:[numberOfItems intValue]+
\gray71\fc5\cf5 1
\gray0\fc2\cf2 ];\
	[
\b self
\b0  displayItem:i];\
	
\b return
\b0  
\b self
\b0 ;\
\}\
\

\fs36 - (void)saveCurrentItem\

\fs28 \{\
	SyntaxItem *item = [itemList objectAt:[itemNumber intValue]-
\gray71\fc5\cf5 1
\gray0\fc2\cf2 ];\
	\
	[item setRegularExpression:regularExpression];\
	[item setMatchedSampleText:matchedSampleText];\
\}\
\

\fs36 - (void)displayItem:(int)i\

\fs28 \{\
	SyntaxItem *item = [itemList objectAt:i
\gray71\fc5\cf5 -1
\gray0\fc2\cf2 ];\
	\
	[itemNumber setIntValue:i];\
	\
	
\b if
\b0  ([item getRegularExpression]) \{\
		[regularExpression readText:[[item getRegularExpression] stream]];\
		[regularExpression setFont:[[item getRegularExpression] font]];\
		[regularExpression display];\
	\} 
\b else
\b0 \
		[regularExpression setText:
\gray550\fc3\cf3 ""
\gray0\fc2\cf2 ];\
\
	
\b if
\b0  ([item getMatchedSampleText]) \{\
		[matchedSampleText readText:[[item getMatchedSampleText] stream]];\
		[matchedSampleText setFont:[[item getMatchedSampleText] font]];\
		[matchedSampleText setTextColor:[[item getMatchedSampleText] textColor]];\
		[matchedSampleText display];\
		[matchedColorWell setColor:[matchedSampleText textColor]];\
	\} 
\b else
\b0 \
		[matchedSampleText setText:
\gray550\fc3\cf3 ""
\gray0\fc2\cf2 ];\
\}\
\

\fs36 - (BOOL)openDefaultConfiguration\

\fs28 \{\
	
\b if
\b0  (![defaultConfiguration stringValue] || !strlen([defaultConfiguration stringValue])) \{\

\pard\tx520\tx1060\tx1600\tx2120\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\fc2\cf2 		[configurationWindow setTitle:""];\

\pard\tx520\tx1060\tx1600\tx2120\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\fc2\cf2 		
\b return
\b0  
\gray93\fc4\cf4 NO
\gray0\fc2\cf2 ;\
	\}\
	
\i\gray300\fc1\cf1 // load default configuration if not already loaded
\i0\gray0\fc2\cf2 \
	
\b if
\b0  (strcmp([defaultConfiguration stringValue], [configurationWindow title]))\
		[
\b self
\b0  openNamedConfiguration:[defaultConfiguration stringValue]];\
	
\b return
\b0  
\gray93\fc4\cf4 YES
\gray0\fc2\cf2 ;\
\}\
\

\fs36 - openConfiguration:sender\

\fs28 \{\
	
\b const
\b0  
\b char
\b0  *
\b const
\b0  types[
\gray71\fc5\cf5 2
\gray0\fc2\cf2 ] = \{
\gray550\fc3\cf3 "syntax"
\gray0\fc2\cf2 , 
\gray93\fc4\cf4 NULL
\gray0\fc2\cf2 \};\
	
\b id
\b0  openPanel = [OpenPanel new];\
	\
	[openPanel allowMultipleFiles:
\gray93\fc4\cf4 NO
\gray0\fc2\cf2 ];\
	
\b if
\b0  (![openPanel runModalForTypes:types])\
		
\b return
\b0  
\b self
\b0 ;\
	[
\b self
\b0  openNamedConfiguration:[openPanel filename]];\
	[configurationWindow makeKeyAndOrderFront:
\b self
\b0 ];\
	
\b return
\b0  
\b self
\b0 ;\
\}\
\

\fs36 - (void)openNamedConfiguration:(const char *)name\

\fs28 \{\
	NXTypedStream *stream;\
	Text *text;\
	NXColor color;\
		\
	[configurationWindow setTitle:name];\
	
\b if
\b0  (!(stream = NXOpenTypedStreamForFile(name, 
\gray93\fc4\cf4 NX_READONLY
\gray0\fc2\cf2 ))) \{\
		NXRunAlertPanel(
\gray550\fc3\cf3 "Error"
\gray0\fc2\cf2 , 
\gray550\fc3\cf3 "Can't open %s."
\gray0\fc2\cf2 , 
\gray93\fc4\cf4 NULL
\gray0\fc2\cf2 , 
\gray93\fc4\cf4 NULL
\gray0\fc2\cf2 , 
\gray93\fc4\cf4 NULL
\gray0\fc2\cf2 , name);\
		[
\b self
\b0  newConfiguration:
\b self
\b0 ];\
		
\b return
\b0 ;\
	\}\
	\
	text = NXReadObject(stream);\
	[unmatchedSampleText readText:[text stream]];\
	[unmatchedSampleText setFont:[text font]];\
	color = NXReadColor(stream);\
	[unmatchedSampleText setTextColor:color];\
	[unmatchedSampleText display];\
	[unmatchedColorWell setColor:color];\
	[text free];\
	\
	[itemList free];\
	itemList = NXReadObject(stream);\
	NXCloseTypedStream(stream);\
	[numberOfItems setIntValue:[itemList count]];\
	
\b if
\b0  ([itemList count] < 
\gray71\fc5\cf5 1
\gray0\fc2\cf2 ) \{\
		NXRunAlertPanel(
\gray550\fc3\cf3 "Error"
\gray0\fc2\cf2 , 
\gray550\fc3\cf3 "No items in configuration file"
\gray0\fc2\cf2 , 
\gray93\fc4\cf4 NULL
\gray0\fc2\cf2 , 
\gray93\fc4\cf4 NULL
\gray0\fc2\cf2 , 
\gray93\fc4\cf4 NULL
\gray0\fc2\cf2 );\
		
\b return
\b0 ;\
	\}\
	[
\b self
\b0  displayItem:
\gray71\fc5\cf5 1
\gray0\fc2\cf2 ];\
	
\b return
\b0 ;\
\}\
\

\fs36 - newConfiguration:sender\

\fs28 \{\
	[configurationWindow makeKeyAndOrderFront:
\b self
\b0 ];\
	[itemList freeObjects];\
	[itemList addObject:[[SyntaxItem alloc] init]];\
	[numberOfItems setIntValue:
\gray71\fc5\cf5 1
\gray0\fc2\cf2 ];\
	[configurationWindow setTitle:
\gray550\fc3\cf3 "Untitled"
\gray0\fc2\cf2 ];\
	[
\b self
\b0  displayItem:
\gray71\fc5\cf5 1
\gray0\fc2\cf2 ];\
	[unmatchedSampleText setText:
\gray550\fc3\cf3 "unmatched text"
\gray0\fc2\cf2 ];\
	
\b return
\b0  
\b self
\b0 ;\
\}\
\

\fs36 - saveConfiguration:sender\

\fs28 \{\
	NXTypedStream *stream;\
	\
	[configurationWindow makeKeyAndOrderFront:
\b self
\b0 ];\
	
\b if
\b0  (!strcmp([configurationWindow title], 
\gray550\fc3\cf3 "Untitled"
\gray0\fc2\cf2 ))\
		[
\b self
\b0  getConfigurationName];\
	
\b if
\b0  (!(stream = NXOpenTypedStreamForFile([configurationWindow title], 
\gray93\fc4\cf4 NX_WRITEONLY
\gray0\fc2\cf2 ))) \{\
		NXRunAlertPanel(
\gray550\fc3\cf3 "Error"
\gray0\fc2\cf2 , 
\gray550\fc3\cf3 "Can't open %s."
\gray0\fc2\cf2 , 
\gray93\fc4\cf4 NULL
\gray0\fc2\cf2 , 
\gray93\fc4\cf4 NULL
\gray0\fc2\cf2 , 
\gray93\fc4\cf4 NULL
\gray0\fc2\cf2 , [configurationWindow title]);\
		
\b return
\b0  
\b self
\b0 ;\
	\}\
	[
\b self
\b0  saveCurrentItem];\
	NXWriteRootObject(stream, unmatchedSampleText);\
	NXWriteColor(stream, [unmatchedSampleText textColor]);\
	NXWriteObject(stream, itemList);\
	NXCloseTypedStream(stream);\
\
	
\b return
\b0  
\b self
\b0 ;\
\}\
\

\fs36 - revertToSavedConfiguration:sender\

\fs28 \{\
	[configurationWindow makeKeyAndOrderFront:
\b self
\b0 ];\
	[
\b self
\b0  openNamedConfiguration:[configurationWindow title]];\
	
\b return
\b0  
\b self
\b0 ;\
\}\
\

\fs36 - (BOOL)getConfigurationName\

\fs28 \{\
	
\b id
\b0  savePanel = [SavePanel new];\
\
	[savePanel setRequiredFileType:
\gray550\fc3\cf3 "syntax"
\gray0\fc2\cf2 ];\
	
\b if
\b0  (![savePanel runModalForDirectory:
\gray93\fc4\cf4 NULL
\gray0\fc2\cf2  file:
\gray93\fc4\cf4 NULL
\gray0\fc2\cf2 ])\
		
\b return
\b0  
\gray93\fc4\cf4 NO
\gray0\fc2\cf2 ;\
	[configurationWindow setTitle:[savePanel filename]];\
	
\b return
\b0  
\gray93\fc4\cf4 YES
\gray0\fc2\cf2 ;\
\}\
\

\fs36 - saveAsConfiguration:sender\

\fs28 \{\
	
\b if
\b0  ([
\b self
\b0  getConfigurationName])\
		[
\b self
\b0  saveConfiguration:
\b self
\b0 ];\
	
\b return
\b0  
\b self
\b0 ;\
\}\
\

\fs36 - setDefaultConfigurationName:sender\

\fs28 \{\
	
\b const
\b0  
\b char
\b0  *
\b const
\b0  types[
\gray71\fc5\cf5 2
\gray0\fc2\cf2 ] = \{
\gray550\fc3\cf3 "syntax"
\gray0\fc2\cf2 , 
\gray93\fc4\cf4 NULL
\gray0\fc2\cf2 \};\
	
\b id
\b0  openPanel = [OpenPanel new];\
	\
	[openPanel allowMultipleFiles:
\gray93\fc4\cf4 NO
\gray0\fc2\cf2 ];\
	
\b if
\b0  (![openPanel runModalForTypes:types])\
		
\b return
\b0  
\gray93\fc4\cf4 NO
\gray0\fc2\cf2 ;\
	[defaultConfiguration setStringValue:[openPanel filename]];\
	NXWriteDefault(
\gray550\fc3\cf3 "RTFSyntax"
\gray0\fc2\cf2 , 
\gray550\fc3\cf3 "configuration"
\gray0\fc2\cf2 , [openPanel filename]);\
	
\b return
\b0  
\b self
\b0 ;\
\}\
\

\fs36 - setMatchedColor:sender\

\fs28 \{\
	[matchedSampleText setTextColor:[matchedColorWell color]];\
	[matchedSampleText display];\
	
\b return
\b0  
\b self
\b0 ;\
\}\
\

\fs36 - setUnmatchedColor:sender;\

\fs28 \{\
	[unmatchedSampleText setTextColor:[unmatchedColorWell color]];\
	[unmatchedSampleText display];\
	
\b return
\b0  
\b self
\b0 ;\
\}\
\

\b #define
\b0  
\gray93\fc4\cf4 MAIL_ADDRESS
\gray0\fc2\cf2  
\gray550\fc3\cf3 "rgc@cs.umd.edu"
\gray0\fc2\cf2 \

\b #define
\b0  
\gray93\fc4\cf4 SUBJECT
\gray0\fc2\cf2  
\gray550\fc3\cf3 "Suggestion/Bug for RTFSyntax"
\gray0\fc2\cf2 \
\

\fs36 - suggestion:sender\

\fs28 \{\
	
\b id
\b0  s = [NXApp appSpeaker];\
\
	NXPortFromName(
\gray550\fc3\cf3 "Mail"
\gray0\fc2\cf2 , 
\gray93\fc4\cf4 NULL
\gray0\fc2\cf2 ); 
\i\gray300\fc1\cf1 // make sure app is launched
\i0\gray0\fc2\cf2 \
	[[NXApp appSpeaker] setSendPort:NXPortFromName(
\gray550\fc3\cf3 "MailSendDemo"
\gray0\fc2\cf2 , 
\gray93\fc4\cf4 NULL
\gray0\fc2\cf2 )];\
	[s performRemoteMethod:
\gray550\fc3\cf3 "setTo:"
\gray0\fc2\cf2  with:
\gray93\fc4\cf4 MAIL_ADDRESS
\gray0\fc2\cf2  length:strlen(
\gray93\fc4\cf4 MAIL_ADDRESS
\gray0\fc2\cf2 )+
\gray71\fc5\cf5 1
\gray0\fc2\cf2 ];\
	[s performRemoteMethod:
\gray550\fc3\cf3 "setSubject:"
\gray0\fc2\cf2  with:
\gray93\fc4\cf4 SUBJECT
\gray0\fc2\cf2  length:strlen(
\gray93\fc4\cf4 SUBJECT
\gray0\fc2\cf2 )+
\gray71\fc5\cf5 1
\gray0\fc2\cf2 ];\
	
\b return
\b0  
\b self
\b0 ;\
\}\
\

\b #define
\b0  
\gray93\fc4\cf4 ERRBUF
\gray0\fc2\cf2  
\gray71\fc5\cf5 100
\gray0\fc2\cf2 \
\

\fs36 - colorSyntax:pasteboard userData:(const char *)userData error:(char **)message\

\fs28 \{\
	NXAtom typelist[
\gray71\fc5\cf5 1
\gray0\fc2\cf2 ];\
	
\b const
\b0  NXAtom *types;\
	NXStream *stream;\
	Text *text;\
		\
	
\b for
\b0  (types = [pasteboard types]; *types; types++)\
		
\b if
\b0  (*types == NXRTFPboardType)\
			
\b break
\b0 ;\
	
\b if
\b0  (*types == 
\gray71\fc5\cf5 0
\gray0\fc2\cf2 ) \{\
		*message = 
\gray550\fc3\cf3 "Could not find RTF pasteboard type"
\gray0\fc2\cf2 ;\
		NXRunAlertPanel(
\gray550\fc3\cf3 "Error"
\gray0\fc2\cf2 , *message, 
\gray71\fc5\cf5 0
\gray0\fc2\cf2 , 
\gray71\fc5\cf5 0
\gray0\fc2\cf2 , 
\gray71\fc5\cf5 0
\gray0\fc2\cf2 );\
		
\b return
\b0  
\gray93\fc4\cf4 nil
\gray0\fc2\cf2 ;\
	\}\
\

\pard\tx520\tx1060\tx1600\tx2120\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\b\fc2\cf2 	if
\b0  (![
\b self
\b0  openDefaultConfiguration]) \{\
		*message = 
\gray551\fc3\cf3 "No default configuration."
\gray0\fc2\cf2 ;\
		NXRunAlertPanel(
\gray551\fc3\cf3 "Error"
\gray0\fc2\cf2 , *message, 
\gray71\fc5\cf5 0
\gray0\fc2\cf2 , 
\gray71\fc5\cf5 0
\gray0\fc2\cf2 , 
\gray71\fc5\cf5 0
\gray0\fc2\cf2 );\

\b 		return
\b0  
\gray93\fc4\cf4 nil
\gray0\fc2\cf2 ;\
	\}\

\pard\tx520\tx1060\tx1600\tx2120\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\fc2\cf2 	\
	
\i\gray300\fc1\cf1 // read the rich text from the pasteboard stream and save it in an internal text object
\i0\gray0\fc2\cf2 \
	
\b if
\b0  (!(stream = [pasteboard readTypeToStream:NXRTFPboardType])) \{\
		*message = 
\gray550\fc3\cf3 "Could not read RTF data"
\gray0\fc2\cf2 ;\
		NXRunAlertPanel(
\gray550\fc3\cf3 "Error"
\gray0\fc2\cf2 , *message, 
\gray71\fc5\cf5 0
\gray0\fc2\cf2 , 
\gray71\fc5\cf5 0
\gray0\fc2\cf2 , 
\gray71\fc5\cf5 0
\gray0\fc2\cf2 );\
		
\b return
\b0  
\gray93\fc4\cf4 nil
\gray0\fc2\cf2 ;\
	\}\
	text = [[Text alloc] init];\
	[text setMonoFont:
\gray93\fc4\cf4 NO
\gray0\fc2\cf2 ];\
	NXSeek(stream, 0L, 
\gray93\fc4\cf4 NX_FROMSTART
\gray0\fc2\cf2 );\
	[text readRichText:stream];\
	NXCloseMemory(stream, 
\gray93\fc4\cf4 NX_FREEBUFFER
\gray0\fc2\cf2 ); \
\
	[
\b self
\b0  colorRTF:text];\
		\
	
\i\gray300\fc1\cf1 // copy results to the pasteboard
\i0\gray0\fc2\cf2 \
	typelist[
\gray71\fc5\cf5 0
\gray0\fc2\cf2 ] = NXRTFPboardType;\
	[pasteboard declareTypes:typelist num:
\gray71\fc5\cf5 1
\gray0\fc2\cf2  owner:
\b self
\b0 ];\
	
\b if
\b0  (!(stream = NXOpenMemory(
\gray93\fc4\cf4 NULL
\gray0\fc2\cf2 , 
\gray71\fc5\cf5 0
\gray0\fc2\cf2 , 
\gray93\fc4\cf4 NX_READWRITE
\gray0\fc2\cf2 ))) \{\
		NXRunAlertPanel(
\gray550\fc3\cf3 "Error"
\gray0\fc2\cf2 , 
\gray550\fc3\cf3 "Can't open memory file."
\gray0\fc2\cf2 , 
\gray71\fc5\cf5 0
\gray0\fc2\cf2 , 
\gray71\fc5\cf5 0
\gray0\fc2\cf2 , 
\gray71\fc5\cf5 0
\gray0\fc2\cf2 );\
		[text free];\
		
\b return
\b0  
\b self
\b0 ;\
	\}\
	[text writeRichText:stream];\
	
\b if
\b0  (![pasteboard writeType:NXRTFPboardType fromStream:stream])\
		NXRunAlertPanel(
\gray550\fc3\cf3 "Error"
\gray0\fc2\cf2 , 
\gray550\fc3\cf3 "Can't write results to pasteboard."
\gray0\fc2\cf2 , 
\gray71\fc5\cf5 0
\gray0\fc2\cf2 , 
\gray71\fc5\cf5 0
\gray0\fc2\cf2 , 
\gray71\fc5\cf5 0
\gray0\fc2\cf2 );\
	NXCloseMemory(stream, 
\gray93\fc4\cf4 NX_FREEBUFFER
\gray0\fc2\cf2 );\
	[text free];\
	\
	
\b return
\b0  
\b self
\b0 ;\
\}\
\

\fs36 - (void)colorRTF:(Text *)text\

\i\fs28\gray300\fc1\cf1 /*\
Description:\
	Color the given text object using the default configuration.\
	Uses GNU's regex library (POSIX syntax).\
*/
\i0\gray0\fc2\cf2 \
\{\
	
\b char
\b0  errbuf[
\gray93\fc4\cf4 ERRBUF
\gray0\fc2\cf2 ];\
	
\b typedef
\b0  
\b struct
\b0  \{\
		regex_t re;\
		
\b int
\b0  begin, end;		
\i\gray300\fc1\cf1 // offsets
\i0\gray0\fc2\cf2 \
		
\gray93\fc4\cf4 BOOL
\gray0\fc2\cf2  compute;		
\i\gray300\fc1\cf1 // YES => compute match
\i0\gray0\fc2\cf2 \
		
\gray93\fc4\cf4 BOOL
\gray0\fc2\cf2  done;			
\i\gray300\fc1\cf1 // YES => no more matches
\i0\gray0\fc2\cf2 \
	\} Match;\
	Match *match;\
	
\b char
\b0  *s;\
	
\b int
\b0  length;\
	
\b int
\b0  i;\
	
\b int
\b0  skip = 
\gray71\fc5\cf5 0
\gray0\fc2\cf2 ;\
	
\b int
\b0  errcode;\
\
	
\i\gray300\fc1\cf1 // first set text to be like the unmatched sample text
\i0\gray0\fc2\cf2 \
	length = [text textLength];\
	[text setSel:
\gray71\fc5\cf5 0
\gray0\fc2\cf2   :length];\
	[text setSelColor:[unmatchedSampleText textColor]];\
	[text setSelFont:[unmatchedSampleText font]];\
	[text selectNull];\
	\
	
\i\gray300\fc1\cf1 // allocate the text string and get it
\i0\gray0\fc2\cf2 \
	
\b if
\b0  (!(s = malloc(length + 
\gray71\fc5\cf5 1
\gray0\fc2\cf2 ))) \{\
		NXRunAlertPanel(
\gray550\fc3\cf3 "Error"
\gray0\fc2\cf2 , 
\gray550\fc3\cf3 "malloc() failed."
\gray0\fc2\cf2 , 
\gray71\fc5\cf5 0
\gray0\fc2\cf2 , 
\gray71\fc5\cf5 0
\gray0\fc2\cf2 , 
\gray71\fc5\cf5 0
\gray0\fc2\cf2 );\
		
\b return
\b0 ;\
	\}\
	
\b if
\b0  ([text getSubstring:s start:
\gray71\fc5\cf5 0
\gray0\fc2\cf2  length:length] <= 
\gray71\fc5\cf5 0
\gray0\fc2\cf2 ) \{\
		NXRunAlertPanel(
\gray550\fc3\cf3 "Error"
\gray0\fc2\cf2 , 
\gray550\fc3\cf3 "Can't get substring."
\gray0\fc2\cf2 , 
\gray71\fc5\cf5 0
\gray0\fc2\cf2 , 
\gray71\fc5\cf5 0
\gray0\fc2\cf2 , 
\gray71\fc5\cf5 0
\gray0\fc2\cf2 );\
		free(s);\
		
\b return
\b0 ;\
	\}\
	s[length] = 
\gray71\fc5\cf5 0
\gray0\fc2\cf2 ;\
	\
	
\i\gray300\fc1\cf1 // allocate the match array
\i0\gray0\fc2\cf2 \
	
\b if
\b0  (!(match = malloc(
\b sizeof
\b0 (Match) * [itemList count]))) \{\
		NXRunAlertPanel(
\gray550\fc3\cf3 "Error"
\gray0\fc2\cf2 , 
\gray550\fc3\cf3 "malloc() failed."
\gray0\fc2\cf2 , 
\gray71\fc5\cf5 0
\gray0\fc2\cf2 , 
\gray71\fc5\cf5 0
\gray0\fc2\cf2 , 
\gray71\fc5\cf5 0
\gray0\fc2\cf2 );\
		free(s);\
		
\b return
\b0 ;\
	\}\
	\
	
\i\gray300\fc1\cf1 // compile all of the regular expressions
\i0\gray0\fc2\cf2 \
	
\b for
\b0  (i = 
\gray71\fc5\cf5 0
\gray0\fc2\cf2 ; i < [itemList count]; i++) \{\
		
\b id
\b0  item = [itemList objectAt:i];\
		
\b char
\b0  *reText = [item getRegularExpressionText];\
		
\b int
\b0  reLength = strlen(reText);\
		
\b int
\b0  j, k;\
		\
		
\i\gray300\fc1\cf1 // remove newlines
\i0\gray0\fc2\cf2 \
		
\b for
\b0  (j = 
\gray71\fc5\cf5 0
\gray0\fc2\cf2 , k = 
\gray71\fc5\cf5 0
\gray0\fc2\cf2 ; k < reLength; k++)\
			
\b if
\b0  (reText[k] != '\\n')\
				reText[j++] = reText[k];\
		reText[j] = '\\
\gray71\fc5\cf5 0
\gray0\fc2\cf2 ';\
		reLength = j;\
		\
		
\i\gray300\fc1\cf1 // convert "\\n"'s to newlines
\i0\gray0\fc2\cf2 \
		
\b for
\b0  (j = 
\gray71\fc5\cf5 0
\gray0\fc2\cf2 , k = 
\gray71\fc5\cf5 0
\gray0\fc2\cf2 ; k < reLength; k++)\
			
\b if
\b0  (reText[k] != '\\\\')\
				reText[j++] = reText[k];\
			
\b else
\b0  
\b if
\b0  (k+
\gray71\fc5\cf5 1
\gray0\fc2\cf2  == reLength)\
				reText[j++] = '\\\\';\
			
\b else
\b0  
\b if
\b0  (reText[k+
\gray71\fc5\cf5 1
\gray0\fc2\cf2 ] == 'n') \{\
				reText[j++] = '\\n';\
				k++;\
			\} 
\b else
\b0  \{\
				reText[j++] = '\\\\';\
				reText[j++] = reText[k+
\gray71\fc5\cf5 1
\gray0\fc2\cf2 ];\
				k++;\
			\}			\
		reText[j] = '\\
\gray71\fc5\cf5 0
\gray0\fc2\cf2 ';\
		reLength = j;\
		\
		
\i\gray300\fc1\cf1 // make sure regular expression is not empty
\i0\gray0\fc2\cf2 \
		
\b if
\b0  (!reLength) \{\
			NXRunAlertPanel(
\gray550\fc3\cf3 "Error"
\gray0\fc2\cf2 , 
\gray550\fc3\cf3 "Regular expression %d is empty."
\gray0\fc2\cf2 , 
\gray71\fc5\cf5 0
\gray0\fc2\cf2 , 
\gray71\fc5\cf5 0
\gray0\fc2\cf2 , 
\gray71\fc5\cf5 0
\gray0\fc2\cf2 , i+
\gray71\fc5\cf5 1
\gray0\fc2\cf2 );\
			
\b goto
\b0  cleanup;\
		\}\
		\
		
\i\gray300\fc1\cf1 // compile regular expression
\i0\gray0\fc2\cf2 \
		
\b if
\b0  ((errcode = regcomp(&match[i].re, reText, 
\gray93\fc4\cf4 REG_EXTENDED
\gray0\fc2\cf2  | 
\gray93\fc4\cf4 REG_NEWLINE
\gray0\fc2\cf2 ))) \{\
			regerror(errcode, &match[i].re, errbuf, 
\gray93\fc4\cf4 ERRBUF
\gray0\fc2\cf2 );\
			NXRunAlertPanel(
\gray550\fc3\cf3 "Error"
\gray0\fc2\cf2 , 
\gray550\fc3\cf3 "regcomp() failed -- %s"
\gray0\fc2\cf2 , 
\gray71\fc5\cf5 0
\gray0\fc2\cf2 , 
\gray71\fc5\cf5 0
\gray0\fc2\cf2 , 
\gray71\fc5\cf5 0
\gray0\fc2\cf2 , errbuf);\
			
\b goto
\b0  cleanup;\
		\}\
		match[i].compute = 
\gray93\fc4\cf4 YES
\gray0\fc2\cf2 ;\
		match[i].done = 
\gray93\fc4\cf4 NO
\gray0\fc2\cf2 ;\
	\}\
	\
	
\b for
\b0  (;;) \{\
		
\b int
\b0  j;		
\i\gray300\fc1\cf1 // index of match[] to use
\i0\gray0\fc2\cf2 \
\
		
\i\gray300\fc1\cf1 // compute all regular expressions that need it
\i0\gray0\fc2\cf2 \
  		
\b for
\b0  (i = 
\gray71\fc5\cf5 0
\gray0\fc2\cf2 ; i < [itemList count]; i++)\
			
\b if
\b0  (!match[i].done && match[i].compute) \{\
				regmatch_t rm;\
				
\b if
\b0  (regexec(&match[i].re, s + skip, 
\gray71\fc5\cf5 1
\gray0\fc2\cf2 , &rm, 
\gray71\fc5\cf5 0
\gray0\fc2\cf2 ))\
					match[i].done = 
\gray93\fc4\cf4 YES
\gray0\fc2\cf2 ;\
				
\b else
\b0  \{\
					match[i].begin = rm.rm_so + skip;\
					match[i].end = rm.rm_eo + skip;\
					match[i].compute = 
\gray93\fc4\cf4 NO
\gray0\fc2\cf2 ;\
				\}\
			\}\
			\
 		
\i\gray300\fc1\cf1 // find the first (earliest) match using all regular expressions
\i0\gray0\fc2\cf2 \
		
\i\gray300\fc1\cf1 // if two RE's find match at the same starting offset, choose the longer match
\i0\gray0\fc2\cf2 \
		
\i\gray300\fc1\cf1 // store the result in j
\i0\gray0\fc2\cf2 \
  		
\b for
\b0  (i = 
\gray71\fc5\cf5 0
\gray0\fc2\cf2 , j = 
\gray71\fc5\cf5 0
\gray0\fc2\cf2 ; i < [itemList count]; i++)\
			
\b if
\b0  (!match[i].done) \{\
				j = i;\
				
\b break
\b0 ;\
			\}\
		
\b if
\b0  (i == [itemList count])		
\i\gray300\fc1\cf1 // no matches
\i0\gray0\fc2\cf2 \
			
\b break
\b0 ;\
 		
\b for
\b0  (++i; i < [itemList count]; i++)\
			
\b if
\b0  (!match[i].done && (match[j].begin > match[i].begin || \
				(match[j].begin == match[i].begin && match[j].end < match[i].end)))\
				j = i;\
		\
		
\i\gray300\fc1\cf1 // reset all matches that started before match[j].end
\i0\gray0\fc2\cf2 \
		
\b for
\b0  (i = 
\gray71\fc5\cf5 0
\gray0\fc2\cf2 ; i < [itemList count]; i++)\
			
\b if
\b0  (!match[i].done && match[i].begin < match[j].end)\
				match[i].compute = 
\gray93\fc4\cf4 YES
\gray0\fc2\cf2 ;\
		\
		
\i\gray300\fc1\cf1 // color the matching text
\i0\gray0\fc2\cf2 \
		
\i\gray300\fc1\cf1 // note that these 4 lines take up about 70% of the program's CPU time
\i0\gray0\fc2\cf2 \
		[text setSel:match[j].begin :match[j].end];\
		[text setSelColor:[[[itemList objectAt:j] getMatchedSampleText] textColor]];\
		[text setSelFont:[[[itemList objectAt:j] getMatchedSampleText] font]];\
		[text selectNull];\
		\
		skip = match[j].end;\
	\}\
	\

\gray587\fc6\cf6 cleanup:
\gray0\fc2\cf2 \
	
\i\gray300\fc1\cf1 // free the compiled regular expressions and other stuff
\i0\gray0\fc2\cf2 \
	
\b for
\b0  (i = 
\gray71\fc5\cf5 0
\gray0\fc2\cf2 ; i < [itemList count]; i++)\
		regfree(&match[i].re);\
	free(match);\
\}\
\

\fs36 - showInfo:sender\

\fs28 \{\
    
\b if
\b0  (!infoPanel)\
		infoPanel = [NXApp loadNibSection:
\gray550\fc3\cf3 "InfoPanel.nib"
\gray0\fc2\cf2  owner:
\b self
\b0 ];\
    [infoPanel makeKeyAndOrderFront:
\b self
\b0 ];\
    
\b return
\b0  
\b self
\b0 ;	\
\}\
\

\b @end
\b0 \

}
